﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.IO.Ports;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Modbus.Device;

namespace 工业设备监控系统.configure
{
    //枚举类型
    public enum DataFormat
    {
        ABCD,
        BADC,
        CDAB,
        DCBA
    }

    public class ModbusRtu
    {
        #region 对象或属性

        //建立连接和断开连接

        //读取输出线圈 读取输入线圈
        //定义串口通信的独享
        private SerialPort MyCom = new SerialPort();

        //创建通信超时属性
        public int ReadTimeout { get; set; } = 2000;

        public int WriteTimeout { get; set; } = 2000;

        //读取返回报文超时时间
        public int ReceiveTimeOut { get; set; } = 2000;

        //字节顺序
        public DataFormat DataFormat { get; set; } = DataFormat.ABCD;

        #endregion 对象或属性

        #region 打开关闭窗口

        /// <summary>
        /// 打开串口
        /// </summary>
        /// <param name="iBaudrate">波特率</param>
        /// <param name="iPortName">串口号</param>
        /// <param name="iDataBits">数据位</param>
        /// <param name="iParity">校验位</param>
        /// <param name="iStopBits">停止位</param>
        public IModbusSerialMaster OpenMyCom(int iBaudrate, string iPortName, int iDataBits, Parity iParity, StopBits iStopBits)
        {
            //如果当前串口是打开的，先关闭
            if (MyCom.IsOpen)
            {
                MyCom.Close();
                return null;
            }
            else
            {
                //串口通信对象实例化
                MyCom = new SerialPort(iPortName, iBaudrate, iParity, iDataBits, iStopBits);

                MyCom.ReadTimeout = this.ReadTimeout;
                MyCom.WriteTimeout = this.WriteTimeout;
                //打开串口
                MyCom.Open();
                IModbusSerialMaster master = ModbusSerialMaster.CreateRtu(MyCom);
                return master;
            }
        }

        public void CloseMyCom()
        {
            if (MyCom.IsOpen)
            {
                MyCom.Close();
            }
        }

        #endregion 打开关闭窗口

        #region 读取输出线圈方法 功能码01H

        /// <summary>
        /// 读取输出线圈方法
        /// </summary>
        /// <param name="iDevAdd">从站地址</param>
        /// <param name="iAddress">起始地址</param>
        /// <param name="iLength">长度</param>
        /// <returns></returns>
        public byte[] ReadOutputStatus(int iDevAdd, int iAddress, int iLength)
        {
            //第一步：拼接报文
            //byte[] SendCommand = new byte[8];
            //SendCommand[0] = (byte)iDevAdd;
            //SendCommand[1] = 0x01;
            //SendCommand[2] = (byte)(iAddress / 256);// 第2位：地址高8位
            //SendCommand[3] = (byte)(iAddress % 256); // 第3位：地址低8位
            //SendCommand[4] = (byte)(iLength / 256);
            //SendCommand[5] = (byte)(iLength % 256);
            //byte[] crc = Crc16(SendCommand, 6);
            //SendCommand[6] = crc[0];
            //SendCommand[7] = crc[1];
            ByteArray SendCommand = new ByteArray();
            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x01, (byte)(iAddress / 256), (byte)(iAddress % 256), (byte)(iLength / 256), (byte)(iLength % 256) });
            SendCommand.Add(Crc16(SendCommand.array, 6));
            //第二步：发送报文接收报文
            int byteLength = 0;
            if (iLength % 8 == 0)
            {
                byteLength = iLength / 8;
            }
            else
            {
                byteLength = iLength / 8 + 1;
            }
            byte[] response = new byte[5 + byteLength];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步：解析报文
                return GetByteArray(response, 3, byteLength);
            }
            else
            {
                return null;
            }
        }

        #endregion 读取输出线圈方法 功能码01H

        #region 读取输入线圈方法 功能码02H

        /// <summary>
        /// 读取输入线圈方法
        /// </summary>
        /// <param name="iDevAdd">从站地址</param>
        /// <param name="iAddress">起始地址</param>
        /// <param name="iLength">长度</param>
        /// <returns></returns>
        public byte[] ReadInputStatus(int iDevAdd, int iAddress, int iLength)
        {
            //第一步：拼接报文
            ByteArray SendCommand = new ByteArray();
            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x02, (byte)(iAddress / 256), (byte)(iAddress % 256), (byte)(iLength / 256), (byte)(iLength % 256) });
            SendCommand.Add(Crc16(SendCommand.array, 6));
            //第二步：发送报文接收报文
            int byteLength = 0;
            if (iLength % 8 == 0)
            {
                byteLength = iLength / 8;
            }
            else
            {
                byteLength = iLength / 8 + 1;
            }
            byte[] response = new byte[5 + byteLength];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步：解析报文

                //验证：功能码+字节计数
                if (response[1] == 0x02 && response[2] == byteLength)
                {
                    return GetByteArray(response, 3, byteLength);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion 读取输入线圈方法 功能码02H

        #region 读取保持寄存器 功能码03H

        public byte[] ReadKeepReg(int iDevAdd, int iAddress, int iLength)
        {
            //第一步：拼接报文
            ByteArray SendCommand = new ByteArray();

            SendCommand.Add(new byte[] { (byte)iDevAdd, 0x03, (byte)(iAddress / 256), (byte)(iAddress % 256), (byte)(iLength / 256), (byte)(iLength % 256) });
            SendCommand.Add(Crc16(SendCommand.array, 6));
            //第二步：发送报文接收报文
            int byteLength = iLength * 2;

            byte[] response = new byte[5 + byteLength];

            if (SendData(SendCommand.array, ref response))
            {
                //第三步：解析报文

                //验证：功能码+字节计数
                if (response[1] == 0x03 && response[2] == byteLength)
                {
                    return GetByteArray(response, 3, byteLength);
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        #endregion 读取保持寄存器 功能码03H

        #region 通用发送并接受方法

        /// <summary>
        /// 通用发送并接受方法
        /// </summary>
        /// <param name="SendByte"></param>
        /// <param name="response"></param>
        private bool SendData(byte[] SendByte, ref byte[] response)
        {
            try
            {
                //发送报文
                MyCom.Write(SendByte, 0, SendByte.Length);
            }
            catch (Exception)
            {
                return false;
            }
            //定义一个Buffer
            byte[] buffer = new byte[1024];

            //定义一个内存
            MemoryStream ms = new MemoryStream();

            //定义读取开始时间
            DateTime start = DateTime.Now;

            //【1】获取当前缓冲区的值,判断是否有值，如果有值，读取过来，放到内存中

            //接着再去判断，如果当前缓冲区的值为9，说明读完了

            //【2】如果每次读取都不到，我们用超时时间来做判断
            while (true)
            {
                try
                {
                    if (MyCom.BytesToRead >= 1)
                    {
                        int spcount = MyCom.Read(buffer, 0, buffer.Length);
                        ms.Write(buffer, 0, spcount);
                    }
                    else
                    {
                        //判断是否超时
                        if ((DateTime.Now - start).TotalMilliseconds > this.ReceiveTimeOut)
                        {
                            ms.Dispose();
                            return false;
                        }
                        else if (ms.Length > 0)
                        {
                            break;
                        }
                    }
                }
                catch (Exception)
                {
                    return false;
                }
            }

            response = ms.ToArray();
            ms.Dispose();
            return true;
        }

        #endregion 通用发送并接受方法

        #region 自定义截取字节数组

        /// <summary>
        /// 自定义截取字节数组
        /// </summary>
        /// <param name="dest">目标字节数组</param>
        /// <param name="offset">偏移</param>
        /// <param name="count"></param>
        /// <returns></returns>
        private byte[] GetByteArray(byte[] dest, int offset, int count)
        {
            byte[] res = new byte[count];
            if (dest != null && dest.Length >= offset + count)
            {
                for (int i = 0; i < count; i++)
                {
                    res[i] = dest[offset + i];
                }
                return res;
            }
            else
            {
                return null;
            }
        }

        #endregion 自定义截取字节数组

        #region CRC校验

        private static readonly byte[] aucCRCHi = {
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
                0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
                0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x00,0xC1,0x81,0x40,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40,0x01,0xC0,0x80,0x41,0x01,0xC0,0x80,0x41,
                0x00,0xC1,0x81,0x40
            };

        private static readonly byte[] aucCRCLo = {
                0x00,0xC0,0xC1,0x01,0xC3,0x03,0x02,0xC2,0xC6,0x06,0x07,0xC7,
                0x05,0xC5,0xC4,0x04,0xCC,0x0C,0x0D,0xCD,0x0F,0xCF,0xCE,0x0E,
                0x0A,0xCA,0xCB,0x0B,0xC9,0x09,0x08,0xC8,0xD8,0x18,0x19,0xD9,
                0x1B,0xDB,0xDA,0x1A,0x1E,0xDE,0xDF,0x1F,0xDD,0x1D,0x1C,0xDC,
                0x14,0xD4,0xD5,0x15,0xD7,0x17,0x16,0xD6,0xD2,0x12,0x13,0xD3,
                0x11,0xD1,0xD0,0x10,0xF0,0x30,0x31,0xF1,0x33,0xF3,0xF2,0x32,
                0x36,0xF6,0xF7,0x37,0xF5,0x35,0x34,0xF4,0x3C,0xFC,0xFD,0x3D,
                0xFF,0x3F,0x3E,0xFE,0xFA,0x3A,0x3B,0xFB,0x39,0xF9,0xF8,0x38,
                0x28,0xE8,0xE9,0x29,0xEB,0x2B,0x2A,0xEA,0xEE,0x2E,0x2F,0xEF,
                0x2D,0xED,0xEC,0x2C,0xE4,0x24,0x25,0xE5,0x27,0xE7,0xE6,0x26,
                0x22,0xE2,0xE3,0x23,0xE1,0x21,0x20,0xE0,0xA0,0x60,0x61,0xA1,
                0x63,0xA3,0xA2,0x62,0x66,0xA6,0xA7,0x67,0xA5,0x65,0x64,0xA4,
                0x6C,0xAC,0xAD,0x6D,0xAF,0x6F,0x6E,0xAE,0xAA,0x6A,0x6B,0xAB,
                0x69,0xA9,0xA8,0x68,0x78,0xB8,0xB9,0x79,0xBB,0x7B,0x7A,0xBA,
                0xBE,0x7E,0x7F,0xBF,0x7D,0xBD,0xBC,0x7C,0xB4,0x74,0x75,0xB5,
                0x77,0xB7,0xB6,0x76,0x72,0xB2,0xB3,0x73,0xB1,0x71,0x70,0xB0,
                0x50,0x90,0x91,0x51,0x93,0x53,0x52,0x92,0x96,0x56,0x57,0x97,
                0x55,0x95,0x94,0x54,0x9C,0x5C,0x5D,0x9D,0x5F,0x9F,0x9E,0x5E,
                0x5A,0x9A,0x9B,0x5B,0x99,0x59,0x58,0x98,0x88,0x48,0x49,0x89,
                0x4B,0x8B,0x8A,0x4A,0x4E,0x8E,0x8F,0x4F,0x8D,0x4D,0x4C,0x8C,
                0x44,0x84,0x85,0x45,0x87,0x47,0x46,0x86,0x82,0x42,0x43,0x83,
                0x41,0x81,0x80,0x40
            };

        /// <summary>
        /// CRC校验
        /// </summary>
        /// <param name="pucFrame">字节数组</param>
        /// <param name="usLen">验证长度</param>
        /// <returns>2个字节</returns>
        public static byte[] Crc16(byte[] pucFrame, int usLen)
        {
            int i = 0;
            byte[] res = new byte[2] { 0xFF, 0xFF };
            ushort iIndex;
            while (usLen-- > 0)
            {
                iIndex = (ushort)(res[0] ^ pucFrame[i++]);
                res[0] = (byte)(res[1] ^ aucCRCHi[iIndex]);
                res[1] = aucCRCLo[iIndex];
            }
            return res;
        }

        #endregion CRC校验
    }
}